#ifndef _SCHED_H
#define _SCHED_H

#define NR_TASKS 64
#define HZ 100  // 内核系统运行频率:每秒内发生的时间中断次数（每秒的滴答数）：将1s分割成 HZ 个滴答，即每个滴答为10ms

#define FIRST_TASK task[0]
#define LAST_TASK task[NR_TASKS-1]

#include <linux/head.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <signal.h>

#if (NR_OPEN > 32)
#error "Currently the close-on-exec-flags are in one word, max 32 files/proc"
#endif

#define TASK_RUNNING		0  // 可运行状态、就绪态   正在运行或处于就绪状态：就绪状态是指进程申请到了CPU以外的其他所有资源，正所谓：万事俱备，只欠东风.提醒：一般的操作系统教科书将正在CPU上执 行的进程定义为RUNNING状态、而将可执行但是尚未被调度执行的进程定义为READY状态，这两种状态在Linux下统一为 TASK_RUNNING状态.
#define TASK_INTERRUPTIBLE	1  // 可中断的睡眠状态、等待状态(直到收到一个信号)：处于等待队伍中，等待资源有效时唤醒（比如等待键盘输入、socket连接、信号等等），但不可以被中断唤醒.
#define TASK_UNINTERRUPTIBLE	2  // 不可中断的睡眠状态、等待状态：处于等待队伍中，等待资源有效时唤醒（比如等待键盘输入、socket连接、信号等等），但可以被中断唤醒.一般情况下，进程列表中的绝大多数进程都处于 TASK_INTERRUPTIBLE状态.毕竟皇帝只有一个（单个CPU时），后宫佳丽几千；如果不是绝大多数进程都在睡眠，CPU又怎么响应得过来.
#define TASK_ZOMBIE		3     // 僵尸状态:进程资源用户空间被释放，但内核中的进程PCB并没有释放，等待父进程回收.
#define TASK_STOPPED		4 // 进程被外部程序暂停（如ctrl+z使进程收到SIGSTOP信号，进程会进入到TASK_STOPPED状态），当再次允许时继续执行（进程收到SIGCONT信号，进入TASK_RUNNING状态），因此处于这一状态的进程可以被唤醒.

#ifndef NULL
#define NULL ((void *) 0)
#endif

extern int copy_page_tables(unsigned long from, unsigned long to, long size);
extern int free_page_tables(unsigned long from, unsigned long size);

extern void sched_init(void);
extern void schedule(void);
extern void trap_init(void);
#ifndef PANIC
void panic(const char * str);
#endif
extern int tty_write(unsigned minor,char * buf,int count);

typedef int (*fn_ptr)();

struct i387_struct {
	long	cwd;
	long	swd;
	long	twd;
	long	fip;
	long	fcs;
	long	foo;
	long	fos;
	long	st_space[20];	/* 8*10 bytes for each FP-reg = 80 bytes */
};

struct tss_struct {
	long	back_link;	/* 16 high bits zero */
	long	esp0;
	long	ss0;		/* 16 high bits zero */
	long	esp1;
	long	ss1;		/* 16 high bits zero */
	long	esp2;
	long	ss2;		/* 16 high bits zero */
	long	cr3;
	long	eip;
	long	eflags;
	long	eax,ecx,edx,ebx;
	long	esp;
	long	ebp;
	long	esi;
	long	edi;
	long	es;		/* 16 high bits zero */
	long	cs;		/* 16 high bits zero */
	long	ss;		/* 16 high bits zero */
	long	ds;		/* 16 high bits zero */
	long	fs;		/* 16 high bits zero */
	long	gs;		/* 16 high bits zero */
	long	ldt;		/* 16 high bits zero */
	long	trace_bitmap;	/* bits: trace 0, bitmap 16-31 */
	struct i387_struct i387;
};

struct task_struct {
/* these are hardcoded - don't touch */
	long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	long counter;  /* 剩余的可执行时间片(运行计数器)，初始值=priority */
	long priority; /* 优先级 */
	long signal;
	struct sigaction sigaction[32];
	long blocked;	/* bitmap of masked signals */
/* various fields */
	int exit_code;
	/*
	start_code ：代码段基地址(linux0.11中，代码段和数据段起始基址相同)，是CPU 4G线性地址空间中进程代码段地址
	end_code：代码长度（字节）
	end_data：代码长度+数据长度（字节数）
	brk：程序的总长度（字节）
	start_stack：堆栈段基地址
	依次排列：start_code < end_code < brk(end_data_seg) < start_stack
	*/
	unsigned long start_code,end_code,end_data,brk,start_stack;
	/* 
	进程组、session的概念：https://www.cnblogs.com/klb561/p/12051027.html
		进程组：父进程创建的所有其子进程的集合；进程组ID = 父进程的pid
			进程组就是一系列相互关联的进程集合，系统中的每一个进程也必须从属于某一个进程组；每个进程组中都会有一个唯一的 ID(process group id)，简称 PGID；PGID 一般等同于进程组的创建进程的 Process ID，而这个进进程一般也会被称为进程组先导(process group leader)，同一进程组中除了进程组先导外的其他进程都是其子进程；
		会话：多个进程组的集合，session leader 为创建会话的进程的id，所以sessionID为创建进程的pgrp或pid。进程的leader=1，说明该进程是session leader，该进程所属会话的会话 ID通过session字段查看。
			会话（session）是一个若干进程组的集合，同样的，系统中每一个进程组也都必须从属于某一个会话；一个会话只拥有最多一个控制终端（也可以没有），该终端为会话中所有进程组中的进程所共用。一个会话中前台进程组只会有一个，只有其中的进程才可以和控制终端进行交互；除了前台进程组外的进程组，都是后台进程组；和进程组先导类似，会话中也有会话先导(session leader)的概念，用来表示建立起到控制终端连接的进程。在拥有控制终端的会话中，session leader 也被称为控制进程(controlling process)，一般来说控制进程也就是登入系统的 shell 进程(login shell)；
	
	pid：进程ID
	pgrp：(process group)表示该进程所属进程组的进程组号（pgrp或pgid),一般是进程组leader(进程组的创建进程、也是父进程)的pid，leader一般是组内其他进程的父进程）；同一个进程组中的所有进程的pgrg相同）
	session：表示该进程所属会话的会话 ID (session)。session相同进程组或进程表示同属于一个会话组
	leader：默认值为0，如果当前进程是会话leader，将leader设置为1 ，此时设置current->session = current->pgrp = current->pid;
			leader=1，则表示是当前进程是某个session的leader，该session号等于当前进程的pid
	*/
	long pid,father,pgrp,session,leader;
	/*
	表8-5 与进程相关的用户 ID和组ID  
	《 理解Effective UID(EUID)和Real UID(RUID)： https://www.junmajinlong.com/linux/euid_ruid/ 》
	euid、ruid：它们是针对进程而非针对文件的，即它们是进程属性，而非文件属性。
	
	进程在执行某些涉及权限的操作时，内核将使用进程的【有效用户/组ID】作为凭证来判断是否有权限执行对应操作。
	例如，rm命令删除文件a.txt时，内核将使用rm进程的有效用户ID和有效组ID和a.txt文件的用户ID和组ID做比较，进而再比较对应的权限位，从而判断rm进程是否有权限删除a.txt。

	既然涉及到权限的操作都以有效ID作为依据，那实际ID有什么作用？
	RUID和RGID用于确定进程所属的用户和组，主要用于判断是否有权限向进程发送信号：对于非特权用户，如果发送信号的进程A的RUID和目标进程B的RUID，则进程A可以发送信号。由于子进程会继承父进程的实际ID，所以父子进程的RUID相等，父子进程可互相发信号。

	用户执行程序时，默认将当前用户或指定的用户(如sudo方式)设置为该进程的有效用户ID和实际用户ID。此时它们是相等的。
	进程内部可通过代码修改进程的有效用户ID。例如，以root身份运行的程序，初始时其有效用户ID为0(即root)，具有特权，之后在程序内部修改其有效用户ID为nobody，修改后该进程将失去大量权限。
	也可以在程序文件上设置suid，使得该程序在执行时，其有效用户ID被设置为程序文件的UID。

	uid、gid：实际用户id（ruid - Real UID）、实际组ID（rgid）：指明了文件拥有者和所属用户组
	euid、egid：有效的用户ID：指明访问文件的权限，用于进程访问文件时的许可权判断。通常，进程只能访问进程的有效用户、有效用户组规定的文件或其他允许访问的文件。一般等于uid。
	suid、guid：Set-uid、Set-gid：例如，如果一个程序的宿主是超级用户uid=0，但该程序设置了 set-user-ID 标志，那么当该程序被一个进程运行时，则该进程的有效用户 ID (euid) 就会被设置成超级用户的 ID（0)。于是这个进程就拥有了超级用户的权限。
			一个实际例子就是 Linux 系统的 passwd 命令。该命令是一个设置了 set-user-Id 的程序， 因此允许用户修改自己的口令。因为该程序需要把用户的新口令写入/etc/passwd 文件中，而该文件只有超级用户才有写权限，因此 passwd 程序就需要使用 set-user-ID 标志。
	*/
	unsigned short uid,euid,suid;
	unsigned short gid,egid,sgid;
	long alarm;  /* 表示经过多少滴答后，将alarm信号位置位(定时告警) */
	/*
	utime：User CPU time(滴答数)
	stime：System CPU time(滴答数)
	cstime：Terminated children System CPU time(子进程系统态运行时间，滴答数)
	cutime：Terminated children user CPU time(子进程用户态运行时间，滴答数)

	utime,stime：用户态和内核态(system)的CPU耗时，单位为滴答
	cutime,cstime ：c表示child,waited-for进程，表示当前进程所有waited-for子进程退出后，累计的用户态和内核态(supervisor)的CPU耗时，单位为滴答
	start_time：进程创建时间，单位为滴答
	*/
	long utime,stime,cutime,cstime,start_time; 
	unsigned short used_math;
/* file system info */
	int tty;		/* -1 if no tty, so it must be signed */
	unsigned short umask;
	struct m_inode * pwd;
	struct m_inode * root;  /* 进程的根目录（伪根目录），即进程只能访问该目录中的项而不能后退到其父目录中去。也即对于该进程本目录就如同是文件系统的根目录 */
	struct m_inode * executable; /* 进程正在运行的执行文件的inode */
	unsigned long close_on_exec;
	struct file * filp[NR_OPEN]; /* 文件句柄数组 */
/* ldt for this task： 0 - zero ，1 - cs ，2 - ds&ss */
	struct desc_struct ldt[3];
/* tss for this task */
	struct tss_struct tss;
};

/*
 *  INIT_TASK is used to set up the first task table, touch at
 * your own risk!. Base=0, limit=0x9ffff (=640kB)
 */
#define INIT_TASK \
/* state etc */	{ 0,15,15, \
/* signals */	0,{{},},0, \
/* ec,brk... */	0,0,0,0,0,0, \
/* pid etc.. */	0,-1,0,0,0, \
/* uid etc */	0,0,0,0,0,0, \
/* alarm */	0,0,0,0,0,0, \
/* math */	0, \
/* fs info */	-1,0022,NULL,NULL,NULL,0, \
/* filp */	{NULL,}, \
	{ \
		{0,0}, \
/* ldt */	{0x9f,0xc0fa00}, \
		{0x9f,0xc0f200}, \
	}, \
/*tss*/	{0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
	 0,0,0,0,0,0,0,0, \
	 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
	 _LDT(0),0x80000000, \
		{} \
	}, \
}

extern struct task_struct *task[NR_TASKS];
extern struct task_struct *last_task_used_math;
extern struct task_struct *current;
extern long volatile jiffies;
extern long startup_time;

// 当前时间的时间戳
#define CURRENT_TIME (startup_time+jiffies/HZ)

extern void add_timer(long jiffies, void (*fn)(void));
extern void sleep_on(struct task_struct ** p);
extern void interruptible_sleep_on(struct task_struct ** p);
extern void wake_up(struct task_struct ** p);

/*
 * Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall
 * 4-TSS0, 5-LDT0, 6-TSS1 etc ...
 */
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
// 获取第n个(n号进程)TSS段的选择子（用于定位GDT中的TSS描述符表项）
// 其中的索引值为：（2*n + FIRST_TSS_ENTRY）; 低3位为：000b，即TI=0，RPL=0；
	// FIRST_TSS_ENTRY表示第0个TSS段的索引；(FIRST_TSS_ENTRY<<3)表示第0个TSS的段选择子。
	// (n<<4 + FIRST_TSS_ENTRY<<3) =（2*n + FIRST_TSS_ENTRY)<<3 = (n<<4)+(FIRST_TSS_ENTRY<<3) 表示第n个TSS的段选择子
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
// 获取第n个(n号进程)LDT段的选择子（用于定位GDT中的LDT描述符表项）
// 其中的索引值为：（2*n + FIRST_TSS_ENTRY + 1）; 低3位为：000b，即TI=0，RPL=0
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
// 加载第n个(n号进程)TSS描述符到TR寄存器
#define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
// 加载第n个(n号进程)LDT描述符到LDTR寄存器
#define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
// 取当前运行任务的任务号（是任务数组中的索引值，与进程号pid 不同）
	// 加载TR寄存器中的TSS段的索引号到变量n中（即TSSn中的索引号n）
	// STR(保存任务寄存器数值到指定寄存器中)
	// 计算公式：( TR值 - FIRST_TSS_ENTRY<<3 )>>3 /2  即： ( TR中的索引值 - FIRST_TSS_ENTRY )/2
#define str(n) \
__asm__("str %%ax\n\t" \
	"subl %2,%%eax\n\t" \
	"shrl $4,%%eax" \
	:"=a" (n) \
	:"a" (0),"i" (FIRST_TSS_ENTRY<<3))
/*
 *	switch_to(n) should switch tasks to task nr n, first
 * checking that n isn't the current task, in which case it does nothing.
 * This also clears the TS-flag if the task we switched to has used
 * tha math co-processor latest.
 */

// __tmp用来构造ljmp的操作数。该操作数由4字节偏移和2字节选择符组成。
// 当选择符是TSS选择符时，指令忽略4字节偏移。
// __tmp.a存放的是偏移，__tmp.b的低2字节存放TSS选择符。高两字节为0。
// ljmp跳转到TSS段选择符会造成任务切换到TSS选择符对应的进程。
// ljmp指令格式是：ljmp 16位段选择符,32位偏移，但如果操作数在内存中，顺序正好相反。
 
// %0    内存地址    __tmp.a的地址，用来放偏移
// %1    内存地址    __tmp.b的地址，用来放TSS选择符
// %2    edx            任务号为n的TSS选择符
// %3    ecx            task[n]

// https://www.cnblogs.com/mqmelon/p/4757722.html
// https://blog.csdn.net/qq_40309727/article/details/104504064

// 切换至任务数组中的n号任务，执行新任务 task[n]
#define switch_to(n) {\
/* TSS段选择子*/ struct {long a,b;} __tmp; \ 
__asm__("cmpl %%ecx,current\n\t" \ 
/* 如果不是当前进程，则进行切换 */						"je 1f\n\t" \
/* 把TSS选择符放入__tmp.b中 */						"movw %%dx,%1\n\t" \
/* 将current指向目标任务；ecx为的新进程 */				"xchgl %%ecx,current\n\t" \  
/*进行长跳转，即切换tss。参考图4-37 任务切换操作示意图*/  	"ljmp *%0\n\t" \
	"cmpl %%ecx,last_task_used_math\n\t" \
	"jne 1f\n\t" \
	"clts\n" \
	"1:" \
	::"m" (*&__tmp.a),"m" (*&__tmp.b), \
	"d" (_TSS(n)),"c" ((long) task[n])); \
}

#define PAGE_ALIGN(n) (((n)+0xfff)&0xfffff000)

// // 设置段描述符中的基地址
#define _set_base(addr,base)  \
__asm__ ("push %%edx\n\t" \
	"movw %%dx,%0\n\t" \
	"rorl $16,%%edx\n\t" \
	"movb %%dl,%1\n\t" \
	"movb %%dh,%2\n\t" \
	"pop %%edx" \
	::"m" (*((addr)+2)), \
	 "m" (*((addr)+4)), \
	 "m" (*((addr)+7)), \
	 "d" (base) \
	)

#define _set_limit(addr,limit) \
__asm__ ("push %%edx\n\t" \
	"movw %%dx,%0\n\t" \
	"rorl $16,%%edx\n\t" \
	"movb %1,%%dh\n\t" \
	"andb $0xf0,%%dh\n\t" \
	"orb %%dh,%%dl\n\t" \
	"movb %%dl,%1\n\t" \
	"pop %%edx" \
	::"m" (*(addr)), \
	 "m" (*((addr)+6)), \
	 "d" (limit) \
	)

// 设置段描述符中的基地址
#define set_base(ldt,base) _set_base( ((char *)&(ldt)) , (base) )
#define set_limit(ldt,limit) _set_limit( ((char *)&(ldt)) , (limit-1)>>12 )

/**
#define _get_base(addr) ({\
unsigned long __base; \
__asm__("movb %3,%%dh\n\t" \
	"movb %2,%%dl\n\t" \
	"shll $16,%%edx\n\t" \
	"movw %1,%%dx" \
	:"=d" (__base) \
	:"m" (*((addr)+2)), \
	 "m" (*((addr)+4)), \
	 "m" (*((addr)+7)) \
        :"memory"); \
__base;})
**/

// 从描述符中获取base基地址
static inline unsigned long _get_base(char * addr)
{
         unsigned long __base;
         __asm__("movb %3,%%dh\n\t"
                 "movb %2,%%dl\n\t"
                 "shll $16,%%edx\n\t"
                 "movw %1,%%dx"
                 :"=&d" (__base)
                 :"m" (*((addr)+2)),
                  "m" (*((addr)+4)),
                  "m" (*((addr)+7)));
         return __base;
}

// 从描述符中获取base基地址
#define get_base(ldt) _get_base( ((char *)&(ldt)) )

// 通过指定的段选择符 segment 获取对应的段描述符中的段长值。(输入：段选择符，输出：段限长)
// %0 - 存放段长值(字节数）；%1 - 段选择符segment.
#define get_limit(segment) ({ \
unsigned long __limit; \
__asm__("lsll %1,%0\n\tincl %0":"=r" (__limit):"r" (segment)); \
__limit;})

#endif
